#include "iuInterface.hpp"
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include "iuPacketEthernet.hpp"

unsigned char iuInterface::m_nextNumber = 0;
pthread_mutex_t iuInterface::m_mutex = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief Initialise l'interface
 */
iuInterface::iuInterface()
{
   next = NULL;

   m_isOpen = false;
   m_isActive = false;
   m_number = m_nextNumber++;
   m_port = 0;
   m_mtu = 1500;
}

/**
 * @brief Verifie si la connexion est ouverte
 * @return Renvoie true si la connexion est ouverte, false sinon
 */
bool iuInterface::isOpen()
{
   return m_isOpen;
}

/**
 * @brief Recoit un paquet a partir de l'interface: si l'adresse MAC de destination
 * n'est pas celle de l'interface, le paquet est detruit, et la fonction renvoie NULL.
 * Sinon, la fonction renvoie le paquet encapsule (IP ou ARP) et place le code correspondant
 * au protocole du paquet encapsule dans "protocol"
 * @param protocol l'endroit ou placer le code du protocole du paquet encapsule (0x800 pour IP, Ox806 pour ARP)
 * @return Renvoie le paquet encapsule dans la trame Ethernet, ou NULL si la trame
 * n'etait pas a destination de l'interface
 * @note Le paquet renvoye devra etre libere avec delete
 */
iuPacket *iuInterface::receivePacket(uint16_t *protocol)
{
   uint16_t len;
   iuPacketEthernet *ethPacket;
   iuPacket *dataPacket;
   char *buffer;

   if (!m_isOpen || !m_isActive)
      return false;
   
   //Lit le buffer contenant le prochain paquet
   pthread_mutex_lock(&m_mutex);
   len = receive(&buffer);
   pthread_mutex_unlock(&m_mutex);
   
   if (len > 0)
   {
      //Convertit le buffer en une trame Ethernet
      ethPacket = new iuPacketEthernet();
      ethPacket->fromBuffer(buffer, len);
      free(buffer);
      
      //Si le paquet est destine a l'interface, on renvoie les donnees encapsulees
      if (ethPacket->getDestination().isBroadcast() || ethPacket->getDestination() == m_macAddress)
      {
         dataPacket = ethPacket->getData();
         ethPacket->setData(NULL);
         if (protocol)
            *protocol = ethPacket->getType();
         delete ethPacket;
         return dataPacket;
      }
      //Sinon, on detruit le paquet et on renvoie NULL
      else
      {
         delete ethPacket;
         return NULL;
      }
   }
   else
      return NULL;
}

/**
 * @brief Envoie un paquet a travers l'interface. Le paquet sera encapsule dans une trame Ethernet
 * dont l'adresse de destination sera "dest" et le code protocole sera "protocol"
 * @param packet le paquet a envoyer
 * @param dest la destination du paquet
 * @param protocol le code protocole a inserer dans la trame Ethernet
 * @return Renvoie true si le paquet a bien ete envoye, false sinon
 */
bool iuInterface::sendPacket(iuPacket &packet, const iuMacAddress &dest, uint16_t protocol)
{
   iuPacketEthernet *ethPacket;
   uint16_t len;
   char *buffer;
   bool result;

   if (!m_isOpen || !m_isActive)
      return false;
   
   //Construit la trame Ethernet a envoyer
   ethPacket = new iuPacketEthernet();
   ethPacket->setSource(m_macAddress);
   ethPacket->setDestination(dest);
   ethPacket->setType(protocol);
   ethPacket->setData(&packet);
   
   //Ecrit la trame Ethernet dans un buffer
   len = ethPacket->getBufferLen();
   buffer = (char *)malloc(len);
   ethPacket->toBuffer(buffer);
   
   //Envoie le buffer
   pthread_mutex_lock(&m_mutex);
   result = (send(buffer, len) > 0);
   pthread_mutex_unlock(&m_mutex);
   free(buffer);
   
   //Detruit la trame Ethernet
   ethPacket->setData(NULL);
   delete ethPacket;
   
   return result;
}

/**
 * @brief Teste si l'interface est active ou non
 * @return Renvoie true si l'interface est active, false sinon
 */
bool iuInterface::isActive()
{
   return m_isActive;
}

/**
 * @brief Active ou non l'interface
 * @param active true pour activer l'interface, false pour la desactiver
 */
void iuInterface::setActive(bool active)
{
   m_isActive = active;
}

/**
 * @brief Configure l'interface avec une adresse IP, une adresse de reseau,
 * une adresse de broadcast et un MTU
 * @param ip l'adresse IP de l'interface
 * @param netmask le masque de reseau de l'interface
 * @param broadcast l'adresse de broadcast de l'interface
 * @param mtu le MTU de l'interface
 */
void iuInterface::configure(const iuIPAddress &ip, const iuIPAddress &netmask, const iuIPAddress &broadcast, uint16_t mtu)
{
   m_ip = ip;
   m_netmask = netmask;
   m_broadcast = broadcast;
   m_mtu = mtu;
   
   m_macAddress.build(m_ip, getpid(), m_number);
}

/**
 * @brief Recupere le numero de l'interface (0 pour eth0, 1 pour eth1...)
 * @return Renvoie le numero de l'interface
 */
unsigned char iuInterface::getNumber()
{
   return m_number;
}

/**
 * @brief Recupere le port de l'interface
 * @return Renvoie le port de l'interface
 */
uint16_t iuInterface::getPort()
{
   return m_port;
}

/**
 * @brief Recupere l'adresse MAC de l'interface
 * @return Renvoie l'adresse MAC de l'interface
 */
const iuMacAddress &iuInterface::getMAC()
{
   return m_macAddress;
}

/**
 * @brief Recupere l'adresse IP de l'interface
 * @return Renvoie l'adresse IP de l'interface
 */
const iuIPAddress &iuInterface::getIP()
{
   return m_ip;
}

/**
 * @brief Recupere le masque de reseau de l'interface
 * @return Renvoie le masque de reseau de l'interface
 */
const iuIPAddress &iuInterface::getNetmask()
{
   return m_netmask;
}

/**
 * @brief Recupere l'adresse de broadcast de l'interface
 * @return Renvoie l'adresse de broadcast de l'interface
 */
const iuIPAddress &iuInterface::getBroadcast()
{
   return m_broadcast;
}

/**
 * @brief Recupere le MTU de l'interface
 * @return Renvoie le MTU de l'interface
 */
uint16_t iuInterface::getMTU()
{
   return m_mtu;
}
